package com.common.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;

import com.esotericsoftware.reflectasm.MethodAccess;
import com.google.common.collect.Lists;

/**
 * 属性装换操作
 *
 * @author fengcl
 *
 */
public class BeanUtils {

    private static Map<String, MethodAccess> methodMap = new ConcurrentHashMap<String, MethodAccess>();

    private static Map<String, Map<Type, Integer>> methodIndexMap = new ConcurrentHashMap<String, Map<Type, Integer>>();

    private static Map<String, List<String>> fieldMap = new ConcurrentHashMap<String, List<String>>();

    /**
     * 复制属性值 忽略 _ 符合
     *
     * @param desc
     * @param orgi
     */
    public static void copyProperties(Object desc, Object orgi) {
        try {
            copyProperties(desc, orgi, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 复制属性值 忽略 _ 符合
     *
     * @param desc
     * @param orgi
     * @throws Exception
     */
    private static void copyProperties(Object desc, Object orgi, boolean ignoreCase) throws Exception {
        if(desc==null || orgi==null){
            return ;
        }
        MethodAccess descMethodAccess = methodMap.get(desc.getClass().getName()+ignoreCase);

        if (descMethodAccess == null) {
            descMethodAccess = cache(desc, ignoreCase);
        }
        MethodAccess orgiMethodAccess = methodMap.get(orgi.getClass().getName()+ignoreCase);
        if (orgiMethodAccess == null) {
            orgiMethodAccess = cache(orgi, ignoreCase);
        }

        List<String> fieldList = fieldMap.get(orgi.getClass().getName()+ignoreCase);
        for (String field : fieldList) {

            String getKey = orgi.getClass().getName() + "." + "get" + field;
            String setkey = desc.getClass().getName() + "." + "set" + field;
            Map<Type, Integer> setIndexmap = methodIndexMap.get(setkey);
            Map<Type, Integer> getIndexmap = methodIndexMap.get(getKey);

            if(setIndexmap != null && getIndexmap!=null){
                if (needReFixList(setIndexmap, getIndexmap)) {
                    Type descclas = (Type) setIndexmap.keySet().toArray()[0];
                    int getIndex = getIndexmap.get(getIndexmap.keySet().toArray()[0]);
                    List<?> li = copyList((List<?>) orgiMethodAccess.invoke(orgi, getIndex),
                            Class.forName(((ParameterizedType) descclas).getActualTypeArguments()[0].getTypeName()) ,ignoreCase ,false );
                    Integer setIndex = setIndexmap.get(setIndexmap.keySet().toArray()[0]);
                    descMethodAccess.invoke(desc, setIndex.intValue(), li);

                } else if (needFixObject(setIndexmap, getIndexmap)) {
                    Class<?> descclas = (Class<?>) setIndexmap.keySet().toArray()[0];
                    int getIndex = getIndexmap.get(getIndexmap.keySet().toArray()[0]);
                    Object d = Class.forName(descclas.getTypeName()).newInstance();
                    Object o = orgiMethodAccess.invoke(orgi, getIndex) ;
                    if(o==null){
                        continue ;
                    }
                    copyProperties(d, o, ignoreCase);
                    Integer setIndex = setIndexmap.get(setIndexmap.keySet().toArray()[0]);
                    descMethodAccess.invoke(desc, setIndex.intValue(), d);
                } else  {
                    Integer setIndex = setIndexmap.get(setIndexmap.keySet().toArray()[0]);
                    int getIndex = getIndexmap.get(getIndexmap.keySet().toArray()[0]);
                    descMethodAccess.invoke(desc, setIndex.intValue(), orgiMethodAccess.invoke(orgi, getIndex));
                }
            }
        }
    }

    private static <T> T copyProperties(Class<T> targetClass, Object orgi, boolean ignoreCase) {
        T toObj = null ;
        if(orgi!=null){
            try {
                toObj = targetClass.newInstance();
                if (ignoreCase) {
                    BeanUtils.copyPropertiesIgnoreCase(toObj, orgi);
                } else {
                    BeanUtils.copyProperties(toObj, orgi);
                }
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    throw e ;
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
        return toObj ;
    }

    /**
     * 复制属性值 忽略大小写与 _符号
     *
     * @param desc
     * @param orgi
     */
    public static void copyPropertiesIgnoreCase(Object desc, Object orgi) {
        try {
            copyProperties(desc, orgi, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static <T> T copyPropertiesIgnoreCase(Class<T> targetClass, Object orgi) {
        return copyProperties(targetClass, orgi ,true) ;
    }

    private static MethodAccess cache(Object orgi, boolean ignoreCase) {
        synchronized (orgi.getClass()) {
            MethodAccess methodAccess = MethodAccess.get(orgi.getClass());
            Field[] fields = orgi.getClass().getDeclaredFields();
            List<String> fieldList = new ArrayList<String>(fields.length);
            for (Field field : fields) {
                if (Modifier.isPrivate(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) {
                    // 非公共私有变量
                    String fieldName = StringUtils.capitalize(field.getName());
                    int getIndex = methodAccess.getIndex("get" + fieldName);
                    int setIndex = methodAccess.getIndex("set" + fieldName);
                    String keyname = fieldName.replaceAll("[^\\da-zA-Z]", "");

                    if (ignoreCase) {
                        keyname = keyname.toLowerCase();
                    }
                    Map<Type, Integer> getmapvo = new HashMap<Type, Integer>();
                    getmapvo.put(field.getGenericType(), getIndex);
                    methodIndexMap.put(orgi.getClass().getName() + "." + "get" + keyname, getmapvo);
                    Map<Type, Integer> setmapvo = new HashMap<Type, Integer>();
                    setmapvo.put(field.getGenericType(), setIndex);
                    methodIndexMap.put(orgi.getClass().getName() + "." + "set" + keyname, setmapvo);
                    fieldList.add(keyname);
                }
            }
            fieldMap.put(orgi.getClass().getName()+ignoreCase, fieldList);
            methodMap.put(orgi.getClass().getName()+ignoreCase, methodAccess);
            return methodAccess;
        }
    }

    private static boolean needFixObject(Map<Type, Integer> desctype, Map<Type, Integer> orgitype) {
        boolean res = false;
        Type descclas = (Type) desctype.keySet().toArray()[0];
        Type orgiclas = (Type) orgitype.keySet().toArray()[0];
        if (!descclas.equals(orgiclas)) {
            res = true;
        }
        return res;
    }

    private static boolean needReFixList(Map<Type, Integer> desctype, Map<Type, Integer> orgitype) {
        boolean res = false;
        Type descclas = (Type) desctype.keySet().toArray()[0];
        Type orgiclas = (Type) orgitype.keySet().toArray()[0];
        if (descclas.getTypeName().contains(List.class.getName()) && !descclas.equals(orgiclas)) {
            return true;
        }

        return res;
    }

    /**
     * VO,PO互相转换
     *
     * @param fromList
     *            源List
     * @param targetClass
     *            目标类型
     *            是否忽略大小写
     * @param immutable
     *            是否返回不可修改的ImmutableList
     * @return
     */
    public static <T> List<T> copyList(List<?> fromList, Class<T> targetClass, boolean ignoreCase, boolean immutable) {
        if(fromList==null){
            return null ;
        }
        List<T> resultList = Lists.transform(fromList, fromObj -> {

            T toObj = copyProperties(targetClass, fromObj, ignoreCase) ;

            return toObj;
        });

        if (immutable) {
            return resultList;
        } else {
            return Lists.newArrayList(resultList);
        }
    }


    /**
     *
     * @param fromList
     * @param targetClass
     * @return
     */
    public static <T> List<T> copyList(List<?> fromList, Class<T> targetClass) {
        // 忽略大小写,可修改
        return copyList(fromList, targetClass, true, false);
    }
}

